// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
#pragma once

#include <turbo/utility/status.h>
#include <cantor/proto/value.pb.h>
#include <turbo/container/span.h>
#include <cantor/type/promote.h>

#include "type.h"
#include "cantor/transform/functions/ctor_json_array_func_expr.h"

namespace cantor {
    class ValueView;

    class Value {
    public:
        Value() = default;

        ~Value() = default;

        Value(const Value &other) = delete;

        Value(Value &&other) = delete;

        Value &operator=(const Value &other) = delete;

        Value &operator=(Value &&other) = delete;


        turbo::Status initialize(const cantor::pb::Value &value);

        turbo::Status initialize(cantor::pb::Value &&value);

    public:
        static turbo::Status initialize_valid(const cantor::pb::Value &value);

    private:
        friend class ValueView;
        cantor::pb::Value _value;
    };

    class ValueView {
    public:
        static turbo::Result<ValueView> create(const pb::Value &value, bool sure_valid = false);

        bool is_null() const;

        pb::TypeKind type() const;

        turbo::Result<int8_t> as_int8() const;

        turbo::Result<int8_t> as_int16() const;

        turbo::Result<int8_t> as_int32() const;

        turbo::Result<int8_t> as_int64() const;

        turbo::Result<turbo::int128> as_int128() const;

        turbo::Result<int8_t> as_uint8() const;

        turbo::Result<int8_t> as_uint16() const;

        turbo::Result<int8_t> as_uint32() const;

        turbo::Result<int8_t> as_uint64() const;

        turbo::Result<turbo::uint128> as_uint128() const;

        turbo::Result<float> as_fp32() const;

        turbo::Result<double> as_fp64() const;

        turbo::Result<std::string_view> as_string() const;

        turbo::Result<turbo::span<const uint8_t> > as_binary() const;

        turbo::Result<turbo::Time> as_timestamp() const;

        turbo::Result<turbo::Duration> as_duration() const;

        turbo::Result<bool> as_bool() const;

        turbo::Result<turbo::CivilSecond> as_datatime() const;

        turbo::Result<turbo::CivilSecond> as_interval() const;

        turbo::Result<int32_t> as_enum() const;

        turbo::Result<turbo::span<const pb::Value> > as_array() const;

        turbo::Result<turbo::span<const pb::Value::MapPair> > as_map() const;

        turbo::Result<turbo::span<const pb::Value> > as_set() const;

        turbo::Result<turbo::span<const std::string> > as_struct_names() const;

        turbo::Result<turbo::span<const pb::Value> > as_struct_values() const;

        turbo::Result<std::string_view> as_union_name() const;

        turbo::Result<const pb::Value *> as_union_value() const;

        turbo::Result<turbo::span<const pb::Value> > as_tuple_values() const;

        turbo::Result<std::string_view> as_json() const;

        turbo::Result<std::string_view> as_proto_uri() const;

        turbo::Result<turbo::span<const uint8_t> > as_proto_data() const;

        template<typename T>
        turbo::Result<T> as_type() const;

    private:
        ValueView(const pb::Value &value) : _value(value) {
        }

    private:
        const pb::Value &_value;
    };

    template<typename T>
    struct CppTypeConvertTag;

    template<>
    struct CppTypeConvertTag<int8_t> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_INT8;
    };

    template<>
    struct CppTypeConvertTag<int16_t> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_INT16;
    };

    template<>
    struct CppTypeConvertTag<int32_t> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_INT32;
    };

    template<>
    struct CppTypeConvertTag<int64_t> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_INT64;
    };

    template<>
    struct CppTypeConvertTag<turbo::int128> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_INT128;
    };

    template<>
    struct CppTypeConvertTag<uint8_t> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_UINT8;
    };

    template<>
    struct CppTypeConvertTag<uint16_t> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_UINT16;
    };

    template<>
    struct CppTypeConvertTag<uint32_t> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_UINT32;
    };

    template<>
    struct CppTypeConvertTag<uint64_t> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_UINT64;
    };

    template<>
    struct CppTypeConvertTag<turbo::uint128> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_UINT128;
    };

    template<>
    struct CppTypeConvertTag<float> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_FP32;
    };

    template<>
    struct CppTypeConvertTag<double> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_FP64;
    };

    template<>
    struct CppTypeConvertTag<std::string_view> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_STRING;
    };

    template<>
    struct CppTypeConvertTag<turbo::span<const uint8_t> > {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_BINARY;
    };

    template<>
    struct CppTypeConvertTag<turbo::Time> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_TIMESTAMP;
    };

    template<>
    struct CppTypeConvertTag<turbo::Duration> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_DURATION;
    };

    template<>
    struct CppTypeConvertTag<turbo::CivilSecond> {
        static constexpr pb::TypeKind kind = pb::TypeKind::TYPE_KIND_DATETIME;
    };


    template<pb::TypeKind kind, typename T>
    struct CanConvertTo {
        static constexpr bool value = ConvertCostTrait<kind, CppTypeConvertTag<T>::kind>::cost >= 0;
    };

    template<typename R, typename T>
    struct CppCanConvertTo {
        static constexpr bool value = ConvertCostTrait<CppTypeConvertTag<T>::kind, CppTypeConvertTag<R>::kind>::cost >=
                                      0;
    };

    template<typename T>
    turbo::int128 convert_int128(T t) {
        if constexpr (std::is_same_v<T, turbo::int128>) {
            return t;
        }
        turbo::int128 result(t);
        return result;
    }

    template<typename T>
    turbo::uint128 convert_uint128(T t) {
        if constexpr (std::is_same_v<T, turbo::uint128>) {
            return t;
        }
        turbo::uint128 result(t);
        return result;
    }

    template<typename R, typename T>
    R convert_value(T t) {
        static_assert(CppCanConvertTo<T, R>::value || std::is_same_v<R, T>, "Can't convert to R");
        if constexpr (std::is_same_v<R, turbo::int128>) {
            return convert_int128(t);
        } else if constexpr (std::is_same_v<R, turbo::uint128>) {
            return convert_uint128(t);
        } else if constexpr (std::is_same_v<R, T>) {
            return t;
        }
        return static_cast<R>(t);
    }

    template<typename T, typename R>
    turbo::Result<R> convert_numeric_impl(T v) {
        if constexpr (CppCanConvertTo<T, R>::value) {
            return convert_value<R>(v);
        } else {
            return turbo::invalid_argument_error("cannot convert to target type");
        }
    }

    template<typename T, typename R>
    turbo::Result<R> proxy_impl(T v) {
        if constexpr (std::is_same_v<T, R>) {
            return v;
        } else {
            return turbo::invalid_argument_error("type not match");
        }
    }

    struct EnumValue {
        int32_t value{-1};
    };

    template<typename R>
    turbo::Result<R> ValueView::as_type() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("null value cannot be converted to null");
        }
        switch (_value.type()) {
            case pb::TYPE_KIND_INT8: {
                return convert_numeric_impl<int8_t, R>(_value.int8_value());
            }
            case pb::TYPE_KIND_INT16: {
                return convert_numeric_impl<int16_t, R>(_value.int16_value());
            }
            case pb::TYPE_KIND_INT32: {
                return convert_numeric_impl<int32_t, R>(_value.int32_value());
            }
            case pb::TYPE_KIND_INT64: {
                return convert_numeric_impl<int64_t, R>(_value.int64_value());
            }
            case pb::TYPE_KIND_INT128: {
                turbo::int128 v = turbo::make_int128(_value.int128_value().hi(), _value.int128_value().low());
                return convert_numeric_impl<turbo::int128, R>(v);
            }
            case pb::TYPE_KIND_UINT8: {
                return convert_numeric_impl<uint8_t, R>(_value.uint8_value());
            }
            case pb::TYPE_KIND_UINT16: {
                return convert_numeric_impl<uint16_t, R>(_value.uint16_value());
            }
            case pb::TYPE_KIND_UINT32: {
                return convert_numeric_impl<uint32_t, R>(_value.uint32_value());
            }
            case pb::TYPE_KIND_UINT64: {
                return convert_numeric_impl<uint64_t, R>(_value.uint64_value());
            }
            case pb::TYPE_KIND_UINT128: {
                turbo::uint128 v = turbo::make_uint128(_value.uint128_value().hi(), _value.uint128_value().low());
                return convert_numeric_impl<turbo::uint128, R>(v);
            }
            case pb::TYPE_KIND_FP32: {
                return convert_numeric_impl<float, R>(_value.fp32_value());
            }
            case pb::TYPE_KIND_FP64: {
                return convert_numeric_impl<double, R>(_value.fp64_value());
            }
            case pb::TYPE_KIND_STRING: {
                return proxy_impl<std::string_view, R>(std::string_view(_value.string_value()));
            }
            case pb::TYPE_KIND_BINARY: {
                turbo::span<const uint8_t> span((const uint8_t *) _value.binary_value().data(),
                                                _value.binary_value().size());
                return proxy_impl<turbo::span<const uint8_t>, R>(span);
            }
            case pb::TYPE_KIND_TIMESTAMP: {
                return proxy_impl<turbo::Time, R>(turbo::Time::from_nanoseconds(_value.timestamp_value()));
            }
            case pb::TYPE_KIND_DURATION: {
                return proxy_impl<turbo::Duration, R>(turbo::Duration::nanoseconds(_value.timestamp_value()));
            }
            case pb::TYPE_KIND_BOOLEAN: {
                return proxy_impl<bool, R>(_value.bool_val());
            }
            default:
                return turbo::invalid_argument_error("unsupported numeric type");
        }
    }

    class ValueBuilder {
    public:
        ValueBuilder(cantor::pb::Value &v) : _value(v) {
        }

        ValueBuilder &set_null(pb::TypeKind type) {
            _value.set_is_null(true);
            _value.set_type(type);
            return *this;
        }

        ValueBuilder &set_not_null() {
            _value.set_is_null(false);
            return *this;
        }

        ValueBuilder &set_type(pb::TypeKind type) {
            _value.set_type(type);
            return *this;
        }

        ValueBuilder &set_int8(int8_t v) {
            _value.set_int8_value(v);
            set_not_null().set_type(pb::TYPE_KIND_INT8);
            return *this;
        }

        ValueBuilder &set_int16(int16_t v) {
            _value.set_int16_value(v);
            set_not_null().set_type(pb::TYPE_KIND_INT16);
            return *this;
        }

        ValueBuilder &set_int32(int32_t v) {
            _value.set_int32_value(v);
            set_not_null().set_type(pb::TYPE_KIND_INT32);
            return *this;
        }

        ValueBuilder &set_int64(int64_t v) {
            _value.set_int64_value(v);
            set_not_null().set_type(pb::TYPE_KIND_INT64);
            return *this;
        }

        ValueBuilder &set_int128(turbo::int128 v) {
            _value.mutable_int128_value()->set_hi(turbo::int128_high64(v));
            _value.mutable_int128_value()->set_low(turbo::int128_low64(v));
            set_not_null().set_type(pb::TYPE_KIND_INT128);
            return *this;
        }

        ValueBuilder &set_int128(int64_t h, int64_t l) {
            _value.mutable_int128_value()->set_hi(h);
            _value.mutable_int128_value()->set_low(l);
            set_not_null().set_type(pb::TYPE_KIND_INT128);
            return *this;
        }

        ValueBuilder &set_uint8(uint8_t v) {
            _value.set_uint8_value(v);
            set_not_null().set_type(pb::TYPE_KIND_UINT8);
            return *this;
        }

        ValueBuilder &set_uint16(uint16_t v) {
            _value.set_uint8_value(v);
            set_not_null().set_type(pb::TYPE_KIND_UINT16);
            return *this;
        }

        ValueBuilder &set_uint32(uint32_t v) {
            _value.set_uint8_value(v);
            set_not_null().set_type(pb::TYPE_KIND_UINT32);
            return *this;
        }

        ValueBuilder &set_uint64(uint64_t v) {
            _value.set_uint8_value(v);
            set_not_null().set_type(pb::TYPE_KIND_UINT64);
            return *this;
        }

        ValueBuilder &set_uint128(turbo::uint128 v) {
            _value.mutable_uint128_value()->set_hi(turbo::uint128_high64(v));
            _value.mutable_uint128_value()->set_low(turbo::uint128_low64(v));
            set_not_null().set_type(pb::TYPE_KIND_UINT128);
            return *this;
        }

        ValueBuilder &set_uint128(uint64_t h, uint64_t l) {
            _value.mutable_uint128_value()->set_hi(h);
            _value.mutable_uint128_value()->set_low(l);
            set_not_null().set_type(pb::TYPE_KIND_UINT128);
            return *this;
        }

        ValueBuilder &set_fp32(float v) {
            _value.set_fp32_value(v);
            set_not_null().set_type(pb::TYPE_KIND_FP32);
            return *this;
        }

        ValueBuilder &set_fp64(double v) {
            _value.set_fp64_value(v);
            set_not_null().set_type(pb::TYPE_KIND_FP64);
            return *this;
        }

        ValueBuilder &set_string(std::string_view str) {
            _value.set_string_value(str.data(), str.size());
            set_not_null().set_type(pb::TYPE_KIND_STRING);
            return *this;
        }

        template<typename... Args>
        ValueBuilder &set_string(const Args &... args) {
            _value.set_string_value(turbo::StringBuilder::create(args...));
            set_not_null().set_type(pb::TYPE_KIND_STRING);
            return *this;
        }

        template<typename... Args>
        ValueBuilder &set_format_string(const turbo::FormatSpec<Args...> &fmt, const Args &... args) {
            _value.set_string_value(turbo::str_format(fmt, args...));
            set_not_null().set_type(pb::TYPE_KIND_STRING);
            return *this;
        }

        ValueBuilder &set_binary(turbo::span<const uint8_t> str) {
            _value.set_binary_value(str.data(), str.size());
            set_not_null().set_type(pb::TYPE_KIND_BINARY);
            return *this;
        }

        ValueBuilder &set_binary(turbo::span<uint8_t> str) {
            _value.set_binary_value(str.data(), str.size());
            set_not_null().set_type(pb::TYPE_KIND_BINARY);
            return *this;
        }

        ValueBuilder &set_timestamp(int64_t nano) {
            _value.set_timestamp_value(nano);
            set_not_null().set_type(pb::TYPE_KIND_TIMESTAMP);
            return *this;
        }

        ValueBuilder &set_timestamp(turbo::Time time) {
            _value.set_timestamp_value(turbo::Time::to_nanoseconds(time));
            set_not_null().set_type(pb::TYPE_KIND_TIMESTAMP);
            return *this;
        }

        ValueBuilder &set_duration(int64_t nano) {
            _value.set_duration_value(nano);
            set_not_null().set_type(pb::TYPE_KIND_DURATION);
            return *this;
        }

        ValueBuilder &set_duration(turbo::Duration d) {
            _value.set_duration_value(turbo::Duration::to_nanoseconds(d));
            set_not_null().set_type(pb::TYPE_KIND_DURATION);
            return *this;
        }

        ValueBuilder &set_bool(bool b) {
            _value.set_bool_val(b);
            set_not_null().set_type(pb::TYPE_KIND_BOOLEAN);
            return *this;
        }

        ValueBuilder &set_datetime(int64_t nano) {
            _value.set_datetime_value(nano);
            set_not_null().set_type(pb::TYPE_KIND_DATETIME);
            return *this;
        }

        ValueBuilder &set_datetime(turbo::CivilSecond sec, const turbo::TimeZone &tz) {
            _value.set_datetime_value(turbo::Time::to_nanoseconds(turbo::Time::from_civil(sec, tz)));
            set_not_null().set_type(pb::TYPE_KIND_DATETIME);
            return *this;
        }

        ValueBuilder &set_interval(int64_t sec) {
            _value.set_interval_value(turbo::Duration::to_nanoseconds(turbo::Duration::seconds(sec)));
            set_not_null().set_type(pb::TYPE_KIND_INTERVAL);
            return *this;
        }

        ValueBuilder &set_interval(turbo::Duration d) {
            _value.set_interval_value(turbo::Duration::to_nanoseconds(d));
            set_not_null().set_type(pb::TYPE_KIND_INTERVAL);
            return *this;
        }

        ValueBuilder &set_decimal64(int64_t d) {
            _value.set_decimal64_value(d);
            set_not_null().set_type(pb::TYPE_KIND_DECIMAL64);
            return *this;
        }

        ValueBuilder &set_decimal128(uint64_t h, uint64_t l) {
            _value.mutable_decimal128_value()->set_hi(h);
            _value.mutable_decimal128_value()->set_hi(l);
            set_not_null().set_type(pb::TYPE_KIND_DECIMAL128);
            return *this;
        }

        ValueBuilder &set_enum(int32_t e) {
            _value.set_enum_int_value(e);
            set_not_null().set_type(pb::TYPE_KIND_DECIMAL128);
            return *this;
        }

        ValueBuilder &array_start() {
            set_not_null().set_type(pb::TYPE_KIND_ARRAY);
            return *this;
        }

        ValueBuilder &array_reserve(size_t count) {
            _value.mutable_array_value()->mutable_data()->Reserve(count);
            return *this;
        }

        ValueBuilder array_add() {
            auto ptr = _value.mutable_array_value()->mutable_data()->Add();
            ValueBuilder b(*ptr);
            return b;
        }

        ValueBuilder &map_start() {
            set_not_null().set_type(pb::TYPE_KIND_MAP);
            return *this;
        }

        ValueBuilder &map_reserve(size_t count) {
            _value.mutable_map_value()->mutable_data()->Reserve(count);
            return *this;
        }

        std::pair<ValueBuilder, ValueBuilder> map_add() {
            auto ptr = _value.mutable_map_value()->mutable_data()->Add();
            ValueBuilder key(*ptr->mutable_key());
            ValueBuilder value(*ptr->mutable_value());
            return std::make_pair(std::move(key), std::move(value));
        }

        ValueBuilder &set_start() {
            set_not_null().set_type(pb::TYPE_KIND_SET);
            return *this;
        }

        ValueBuilder &set_reserve(size_t count) {
            _value.mutable_set_value()->mutable_data()->Reserve(count);
            return *this;
        }


        ValueBuilder set_add() {
            auto ptr = _value.mutable_set_value()->mutable_data()->Add();
            ValueBuilder key(*ptr);
            return key;
        }

        ValueBuilder &struct_start() {
            set_not_null().set_type(pb::TYPE_KIND_STRUCT);
            return *this;
        }

        ValueBuilder &struc_reserve(size_t count) {
            _value.mutable_struct_value()->mutable_names()->Reserve(count);
            _value.mutable_struct_value()->mutable_values()->Reserve(count);
            return *this;
        }

        ValueBuilder struct_add(std::string_view name) {
            _value.mutable_struct_value()->mutable_names()->Add()->assign(name.data(), name.size());
            auto ptr = _value.mutable_struct_value()->mutable_values()->Add();
            ValueBuilder key(*ptr);
            return key;
        }

        ValueBuilder set_union(std::string_view name) {
            set_not_null().set_type(pb::TYPE_KIND_UNION);
            _value.mutable_union_value()->set_name(name.data(), name.size());
            auto ptr = _value.mutable_union_value()->mutable_value();
            ValueBuilder builder(*ptr);
            return builder;
        }

        ValueBuilder &tuple_start() {
            set_not_null().set_type(pb::TYPE_KIND_TUPLE);
            return *this;
        }

        ValueBuilder &tuple_reserve(size_t count) {
            _value.mutable_tuple_value()->mutable_values()->Reserve(count);
            return *this;
        }

        ValueBuilder tuple_add() {
            auto ptr = _value.mutable_tuple_value()->mutable_values()->Add();
            ValueBuilder key(*ptr);
            return key;
        }

        ValueBuilder &set_json(std::string_view json) {
            set_not_null().set_type(pb::TYPE_KIND_JSON);
            _value.set_json_value(json.data(), json.size());
            return *this;
        }

        ValueBuilder &set_json(std::string_view uri, turbo::span<uint8_t> data) {
            set_not_null().set_type(pb::TYPE_KIND_PROTO);
            _value.mutable_proto_value()->set_uri(uri.data(), uri.size());
            _value.mutable_proto_value()->set_data(data.data(), data.size());
            return *this;
        }

        ValueBuilder &set_json(std::string_view uri, turbo::span<const uint8_t> data) {
            set_not_null().set_type(pb::TYPE_KIND_PROTO);
            _value.mutable_proto_value()->set_uri(uri.data(), uri.size());
            _value.mutable_proto_value()->set_data(data.data(), data.size());
            return *this;
        }

        turbo::Status set_up(int8_t v) {
            set_int8(v);
            return turbo::OkStatus();
        }
        turbo::Status set_up(int16_t v) {
            set_int16(v);
            return turbo::OkStatus();
        }
        turbo::Status set_up(int32_t v) {
            set_int32(v);
            return turbo::OkStatus();
        }
        turbo::Status set_up(int64_t v) {
            set_int64(v);
            return turbo::OkStatus();
        }

        turbo::Status set_up(turbo::int128 v) {
            set_int128(v);
            return turbo::OkStatus();
        }

        turbo::Status set_up(uint8_t v) {
            set_uint8(v);
            return turbo::OkStatus();
        }

        turbo::Status set_up(uint16_t v) {
            set_uint16(v);
            return turbo::OkStatus();
        }

        turbo::Status set_up(uint32_t v) {
            set_uint32(v);
            return turbo::OkStatus();
        }

        turbo::Status set_up(uint64_t v) {
            set_uint64(v);
            return turbo::OkStatus();
        }

        turbo::Status set_up(turbo::uint128 v) {
            set_uint128(v);
            return turbo::OkStatus();
        }

        turbo::Status set_up(float v) {
            set_fp32(v);
            return turbo::OkStatus();
        }

        turbo::Status set_up(double v) {
            set_fp64(v);
            return turbo::OkStatus();
        }

        turbo::Status set_up(std::string_view v) {
            set_string(v);
            return turbo::OkStatus();
        }
        turbo::Status set_up(turbo::span<const uint8_t> v) {
            set_binary(v);
            return turbo::OkStatus();
        }
        turbo::Status set_up(turbo::span<uint8_t> v) {
            set_binary(v);
            return turbo::OkStatus();
        }

        turbo::Status set_up(bool v) {
            set_bool(v);
            return turbo::OkStatus();
        }
    private:
        cantor::pb::Value &_value;
    };
} // namespace cantor
